package org.liqingye.es.service.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.apache.lucene.search.TotalHits;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.DocWriteRequest.OpType;
import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.liqingye.es.page.Page;
import org.liqingye.es.service.BaseSearchService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BaseSearchServiceImpl implements BaseSearchService {
    private Logger logger = LoggerFactory.getLogger(BaseSearchServiceImpl.class);

    @Autowired
    private RestHighLevelClient client;

    @Override
    public RestStatus indexRequest(String index, String id, String jsonStr) throws Exception {
        IndexRequest request = new IndexRequest(index);
        request.id(id);
        request.opType(OpType.CREATE);
        request.source(jsonStr, XContentType.JSON);
        try {
            IndexResponse response = client.index(request, RequestOptions.DEFAULT);
            logger.info("info index:" + response.getIndex() + ",id:" + response.getId());
            if (response.getResult() == DocWriteResponse.Result.CREATED) {
                logger.info("Handle (if needed) the case where the document was created for the first time");
            } else if (response.getResult() == DocWriteResponse.Result.UPDATED) {
                logger.info("Handle (if needed) the case where the document was created for the first time");
            }
            return response.status();
        } catch (ElasticsearchException e) {
            if (e.status() == RestStatus.CONFLICT) {
                logger.info("The raised exception indicates that a version conflict error was returned");
            }
            logger.info("------------------" + e.getMessage() + "---------------");
            throw new ElasticsearchException(e);
        }
    }

    @Override
    public RestStatus indexRequest(String index, String id, Map<String, Object> jsonMap) throws Exception {
        IndexRequest request = new IndexRequest(index);
        request.id(id);
        request.opType(OpType.CREATE);
        request.source(jsonMap);
        try {
            IndexResponse response = client.index(request, RequestOptions.DEFAULT);
            logger.info("info index:" + response.getIndex() + ",id:" + response.getId());
            if (response.getResult() == DocWriteResponse.Result.CREATED) {
                logger.info("Handle (if needed) the case where the document was created for the first time");
            } else if (response.getResult() == DocWriteResponse.Result.UPDATED) {
                logger.info("Handle (if needed) the case where the document was created for the first time");
            }
            return response.status();
        } catch (ElasticsearchException e) {
            if (e.status() == RestStatus.CONFLICT) {
                logger.info("The raised exception indicates that a version conflict error was returned");
            }
            logger.info("------------------" + e.getMessage() + "---------------");
            throw new ElasticsearchException(e);
        }
    }

    @Override
    public Map<String, Object> getRequest(String index, String id) throws Exception {
        try {
            GetRequest request = new GetRequest(index);
            request.id(id);
            GetResponse response = client.get(request, RequestOptions.DEFAULT);
            if (response.isExists()) {
                return response.getSourceAsMap();
            }
        } catch (ElasticsearchException exception) {
            if (exception.status() == RestStatus.CONFLICT) {
                logger.info("The raised exception indicates that a version conflict error was returned");
            }
            if (exception.status() == RestStatus.NOT_FOUND) {
                logger.info("Handle the exception thrown because the index does not exist");
            }
            logger.info("------------------" + exception.getMessage() + "---------------");
            throw new ElasticsearchException(exception);
        }
        return null;
    }

    @Override
    public RestStatus deleteResponse(String index, String id) throws Exception {
        DeleteRequest request = new DeleteRequest(index);
        request.id(id);
        try {
            DeleteResponse deleteResponse = client.delete(request, RequestOptions.DEFAULT);
            if (deleteResponse.getResult() == DocWriteResponse.Result.NOT_FOUND) {
                logger.info("Do something if the document to be deleted was not found");
            }
            return deleteResponse.status();
        } catch (ElasticsearchException exception) {
            if (exception.status() == RestStatus.CONFLICT) {
                logger.info("The raised exception indicates that a version conflict error was returned");
            }
            logger.info("------------------" + exception.getMessage() + "---------------");
            throw new ElasticsearchException(exception);
        }
    }

    @Override
    public RestStatus updateRequest(String index, String id, Map<String, Object> jsonMap) throws Exception {
        UpdateRequest request = new UpdateRequest();
        request.index(index);
        request.id(id);
        request.doc(jsonMap);
        try {
            UpdateResponse updateResponse = client.update(request, RequestOptions.DEFAULT);
            return updateResponse.status();
        } catch (ElasticsearchException e) {
            if (e.status() == RestStatus.NOT_FOUND) {
                logger.info("Handle the exception thrown because the document not exist");
            }
            logger.info("------------------" + e.getMessage() + "---------------");
            throw new ElasticsearchException(e);
        }
    }

    @Override
    public RestStatus updateRequest(String index, String id, String jsonStr) throws Exception {
        UpdateRequest request = new UpdateRequest();
        request.index(index);
        request.id(id);
        request.doc(jsonStr, XContentType.JSON);
        try {
            UpdateResponse updateResponse = client.update(request, RequestOptions.DEFAULT);
            return updateResponse.status();
        } catch (ElasticsearchException e) {
            if (e.status() == RestStatus.NOT_FOUND) {
                logger.info("Handle the exception thrown because the document not exist");
            }
            logger.info("------------------" + e.getMessage() + "---------------");
            throw new ElasticsearchException(e);
        }
    }

    @Override
    public List<Map<String, Object>> query(String index, String keyword, List<String> fields, int from, int size,
            List<String> descNames) throws Exception {
        SearchRequest searchRequest = new SearchRequest(index);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        if (!fields.isEmpty()) {
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            for (String field : fields) {
                boolQueryBuilder.should(QueryBuilders.matchPhraseQuery(field, keyword));
            }
            searchSourceBuilder.query(boolQueryBuilder);
        }
        if (!descNames.isEmpty()) {
            for (String descName : descNames) {
                searchSourceBuilder.sort(new FieldSortBuilder(descName).order(SortOrder.DESC));
            }
        }
        searchSourceBuilder.from(from);
        searchSourceBuilder.size(size);
        searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
        searchRequest.source(searchSourceBuilder);

        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        SearchHits hits = searchResponse.getHits();
        List<Map<String, Object>> list = new ArrayList<>();
        for (SearchHit hit : hits.getHits()) {
            list.add(hit.getSourceAsMap());
        }
        return list;
    }

    @Override
    public List<Map<String, Object>> queryHighLightBykeyword(String index, String keyword, List<String> fields,
            int from, int size, List<String> descNames) throws Exception {
        SearchRequest searchRequest = new SearchRequest(index);

        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        if (!fields.isEmpty()) {
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            for (String field : fields) {
                boolQueryBuilder.should(QueryBuilders.matchPhraseQuery(field, keyword));
                HighlightBuilder.Field highlightTitle = new HighlightBuilder.Field(field);
                highlightBuilder.field(highlightTitle);
                highlightBuilder.preTags("<span style='color:green'>");
                highlightBuilder.postTags("</span>");
            }
            searchSourceBuilder.query(boolQueryBuilder);
        }
        if (!descNames.isEmpty()) {
            for (String descName : descNames) {
                searchSourceBuilder.sort(new FieldSortBuilder(descName).order(SortOrder.DESC));
            }
        }
        searchSourceBuilder.highlighter(highlightBuilder);
        searchSourceBuilder.from(from);
        searchSourceBuilder.size(size);
        searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
        searchRequest.source(searchSourceBuilder);
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        SearchHits hits = searchResponse.getHits();
        List<Map<String, Object>> list = new ArrayList<>();
        for (SearchHit hit : hits.getHits()) {
            Map<String, Object> map = hit.getSourceAsMap();
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if (!fields.isEmpty()) {
                for (String field : fields) {
                    HighlightField highlight = highlightFields.get(field);
                    if (highlight != null) {
                        Text[] fragments = highlight.fragments();
                        String fragmentString = fragments[0].string();
                        map.replace(field, fragmentString);
                    }
                }
            }
            list.add(map);
        }
        return list;
    }

    @Override
    public List<Map<String, Object>> query(String index, String keyword, List<String> fields, List<String> descNames)
            throws Exception {
        SearchRequest searchRequest = new SearchRequest(index);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        if (!fields.isEmpty()) {
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            for (String field : fields) {
                boolQueryBuilder.should(QueryBuilders.matchPhraseQuery(field, keyword));
            }
            searchSourceBuilder.query(boolQueryBuilder);
        }
        if (!descNames.isEmpty()) {
            for (String descName : descNames) {
                searchSourceBuilder.sort(new FieldSortBuilder(descName).order(SortOrder.DESC));
            }
        }
        searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
        searchRequest.source(searchSourceBuilder);

        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        SearchHits hits = searchResponse.getHits();
        List<Map<String, Object>> list = new ArrayList<>();
        for (SearchHit hit : hits.getHits()) {
            list.add(hit.getSourceAsMap());
        }
        return list;
    }

    @Override
    public List<Map<String, Object>> queryHighLightBykeyword(String index, String keyword, List<String> fields,
            List<String> descNames) throws Exception {
        SearchRequest searchRequest = new SearchRequest(index);

        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        if (!fields.isEmpty()) {
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            for (String field : fields) {
                boolQueryBuilder.should(QueryBuilders.matchPhraseQuery(field, keyword));
                HighlightBuilder.Field highlightTitle = new HighlightBuilder.Field(field);
                highlightBuilder.field(highlightTitle);
                highlightBuilder.preTags("<span style='color:#366df0'>");
                highlightBuilder.postTags("</span>");
            }
            searchSourceBuilder.query(boolQueryBuilder);
        }
        if (!descNames.isEmpty()) {
            for (String descName : descNames) {
                searchSourceBuilder.sort(new FieldSortBuilder(descName).order(SortOrder.DESC));
            }
        }
        searchSourceBuilder.highlighter(highlightBuilder);
        searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
        searchRequest.source(searchSourceBuilder);
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        SearchHits hits = searchResponse.getHits();
        List<Map<String, Object>> list = new ArrayList<>();
        for (SearchHit hit : hits.getHits()) {
            Map<String, Object> map = hit.getSourceAsMap();
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if (!fields.isEmpty()) {
                for (String field : fields) {
                    HighlightField highlight = highlightFields.get(field);
                    if (highlight != null) {
                        Text[] fragments = highlight.fragments();
                        String fragmentString = fragments[0].string();
                        map.replace(field, fragmentString);
                    }
                }
            }
            list.add(map);
        }
        return list;
    }

    @Override
    public boolean deleteResponse(String index) throws Exception {
        DeleteIndexRequest request = new DeleteIndexRequest(index);
        AcknowledgedResponse deleteResponse = client.indices().delete(request, RequestOptions.DEFAULT);
        return deleteResponse.isAcknowledged();
    }

    @Override
    public Page<Map<String, Object>> queryHighLightPageBykeyword(String index, String keyword, List<String> fields,
            int pageNum, int pageSize, List<String> descNames) throws Exception {
        SearchRequest searchRequest = new SearchRequest(index);

        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        if (!fields.isEmpty()) {
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            for (String field : fields) {
                boolQueryBuilder.should(QueryBuilders.matchPhraseQuery(field, keyword));
                HighlightBuilder.Field highlightTitle = new HighlightBuilder.Field(field);
                highlightBuilder.field(highlightTitle);
                highlightBuilder.preTags("<span style='color:green'>");
                highlightBuilder.postTags("</span>");
            }
            searchSourceBuilder.query(boolQueryBuilder);
        }
        if (!descNames.isEmpty()) {
            for (String descName : descNames) {
                searchSourceBuilder.sort(new FieldSortBuilder(descName).order(SortOrder.DESC));
            }
        }
        searchSourceBuilder.highlighter(highlightBuilder);
        searchSourceBuilder.from((pageNum - 1) * pageSize);
        searchSourceBuilder.size(pageSize);
        searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
        searchRequest.source(searchSourceBuilder);
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        SearchHits hits = searchResponse.getHits();
        TotalHits totalCount = hits.getTotalHits();
        List<Map<String, Object>> list = new ArrayList<>();
        for (SearchHit hit : hits.getHits()) {
            Map<String, Object> map = hit.getSourceAsMap();
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if (!fields.isEmpty()) {
                for (String field : fields) {
                    HighlightField highlight = highlightFields.get(field);
                    if (highlight != null) {
                        Text[] fragments = highlight.fragments();
                        String fragmentString = fragments[0].string();
                        map.replace(field, fragmentString);
                    }
                }
            }
            list.add(map);
        }
        int value = Integer.valueOf(String.valueOf(totalCount.value));
        return new Page<Map<String, Object>>(pageNum, ((value - 1) / pageSize) + 1, value, pageSize, list);
    }

}
